Skip to content
1 change: 1 addition & 0 deletions CMake/Tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ set(tests
stores_test
tile_properties_test
timedemo_test
town_registry_test
townerdat_test
writehero_test
vendor_test
Expand Down
2 changes: 2 additions & 0 deletions Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ set(libdevilutionx_SRCS
levels/themes.cpp
levels/tile_properties.cpp
levels/town.cpp
levels/town_data.cpp
levels/trigs.cpp

lua/autocomplete.cpp
Expand Down Expand Up @@ -127,6 +128,7 @@ set(libdevilutionx_SRCS
lua/modules/render.cpp
lua/modules/system.cpp
lua/modules/towners.cpp
lua/modules/towns.cpp
lua/repl.cpp

monsters/validation.cpp
Expand Down
2 changes: 1 addition & 1 deletion Source/diablo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#endif
#endif

#include <fmt/format.h>

Check warning on line 22 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:22:1 [misc-include-cleaner]

included header format.h is not used directly

#include <config.h>

Expand All @@ -42,7 +42,7 @@
#include "diablo_msg.hpp"
#include "discord/discord.h"
#include "doom.h"
#include "encrypt.h"

Check warning on line 45 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:45:1 [misc-include-cleaner]

included header encrypt.h is not used directly
#include "engine/backbuffer_state.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/demomode.h"
Expand All @@ -61,10 +61,10 @@
#include "hwcursor.hpp"
#include "init.hpp"
#include "inv.h"
#include "levels/drlg_l1.h"

Check warning on line 64 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:64:1 [misc-include-cleaner]

included header drlg_l1.h is not used directly
#include "levels/drlg_l2.h"

Check warning on line 65 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:65:1 [misc-include-cleaner]

included header drlg_l2.h is not used directly
#include "levels/drlg_l3.h"

Check warning on line 66 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:66:1 [misc-include-cleaner]

included header drlg_l3.h is not used directly
#include "levels/drlg_l4.h"

Check warning on line 67 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:67:1 [misc-include-cleaner]

included header drlg_l4.h is not used directly
#include "levels/gendung.h"
#include "levels/setmaps.h"
#include "levels/themes.h"
Expand Down Expand Up @@ -113,10 +113,10 @@
#include "utils/paths.h"
#include "utils/screen_reader.hpp"
#include "utils/sdl_compat.h"
#include "utils/sdl_thread.h"

Check warning on line 116 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:116:1 [misc-include-cleaner]

included header sdl_thread.h is not used directly
#include "utils/status_macros.hpp"
#include "utils/str_cat.hpp"
#include "utils/utf8.hpp"

Check warning on line 119 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:119:1 [misc-include-cleaner]

included header utf8.hpp is not used directly

#ifndef USE_SDL1
#include "controls/touch/gamepad.h"
Expand All @@ -134,8 +134,8 @@
namespace devilution {

uint32_t DungeonSeeds[NUMLEVELS];
std::optional<uint32_t> LevelSeeds[NUMLEVELS];

Check warning on line 137 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:137:6 [misc-include-cleaner]

no header providing "std::optional" is directly included
Point MousePosition;

Check warning on line 138 in Source/diablo.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/diablo.cpp:138:1 [misc-include-cleaner]

no header providing "devilution::Point" is directly included
bool gbRunGameResult;
bool ReturnToMainMenu;
/** Enable updating of player character, set to false once Diablo dies */
Expand Down Expand Up @@ -3219,7 +3219,7 @@

IncProgress();

if (!firstflag && lvldir != ENTRY_LOAD && myPlayer._pLvlVisited[currlevel] && !gbIsMultiplayer)
if (!firstflag && lvldir != ENTRY_LOAD && lvldir != ENTRY_TOWNSWITCH && myPlayer._pLvlVisited[currlevel] && !gbIsMultiplayer)
RETURN_IF_ERROR(LoadLevel());
if (gbIsMultiplayer)
DeltaLoadLevel();
Expand Down
43 changes: 43 additions & 0 deletions Source/interfac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "game_mode.hpp"
#include "headless_mode.hpp"
#include "hwcursor.hpp"
#include "levels/town_data.h"
#include "loadsave.h"
#include "multi.h"
#include "pfile.h"
Expand Down Expand Up @@ -103,6 +104,7 @@ Cutscenes PickCutscene(interface_mode uMsg)
case WM_DIABNEWGAME:
return CutStart;
case WM_DIABRETOWN:
case WM_DIABTOWNSWITCH:
return CutTown;
case WM_DIABNEXTLVL:
case WM_DIABPREVLVL:
Expand Down Expand Up @@ -462,6 +464,34 @@ void DoLoad(interface_mode uMsg)
loadResult = LoadGameLevel(false, ENTRY_MAIN);
if (loadResult.has_value()) IncProgress();
break;
case WM_DIABTOWNSWITCH:
IncProgress();
if (!gbIsMultiplayer) {
pfile_save_level();
} else {
DeltaSaveLevel();
}
IncProgress();
FreeGameMem();
if (!GetTownRegistry().HasTown(DestinationTownID)) {
LogError("WM_DIABTOWNSWITCH: unknown town '{}'", DestinationTownID);
loadResult = tl::make_unexpected<std::string>("Unknown destination town");
break;
}
GetTownRegistry().SetCurrentTown(DestinationTownID);

if (MyPlayer != nullptr) {
const TownConfig &destTown = GetTownRegistry().GetTown(DestinationTownID);
MyPlayer->_pCurrentTownId = destTown.saveId;
}

setlevel = false;
currlevel = 0;
leveltype = DTYPE_TOWN;
IncProgress();
loadResult = LoadGameLevel(false, ENTRY_TOWNSWITCH);
if (loadResult.has_value()) IncProgress();
break;
default:
loadResult = tl::make_unexpected<std::string>("Unknown progress mode");
break;
Expand Down Expand Up @@ -570,6 +600,19 @@ void ProgressEventHandler(const SDL_Event &event, uint16_t modState)

} // namespace

void QueueTownSwitch()
{
if (MyPlayer != nullptr) {
MyPlayer->_pInvincible = true;
SDL_Event event;
CustomEventToSdlEvent(event, WM_DIABTOWNSWITCH);
if (!SDLC_PushEvent(&event)) {
LogError("QueueTownSwitch: {}", SDL_GetError());
SDL_ClearError();
}
}
}

void RegisterCustomEvents()
{
#ifndef USE_SDL1
Expand Down
4 changes: 4 additions & 0 deletions Source/interfac.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum interface_mode : uint8_t {
WM_DIABTOWNWARP,
WM_DIABTWARPUP,
WM_DIABRETOWN,
WM_DIABTOWNSWITCH,
WM_DIABNEWGAME,
WM_DIABLOADGAME,

Expand Down Expand Up @@ -70,6 +71,9 @@ enum Cutscenes : uint8_t {
CutGate,
};

/** @brief Queues WM_DIABTOWNSWITCH for the local player (invincible until load completes). */
void QueueTownSwitch();

void interface_msg_pump();
void IncProgress(uint32_t steps = 1);
void CompleteProgress();
Expand Down
7 changes: 7 additions & 0 deletions Source/levels/gendung.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "levels/drlg_l4.h"
#include "levels/reencode_dun_cels.hpp"
#include "levels/town.h"
#include "levels/town_data.h"
#include "lighting.h"
#include "monster.h"
#include "objects.h"
Expand Down Expand Up @@ -812,6 +813,12 @@ bool IsNearThemeRoom(WorldTilePosition testPosition)

void InitLevels()
{
// Note: InitializeTristram overwrites any existing "tristram" registration.
// Lua mods that called towns.register("tristram", ...) before InitLevels will
// have their config replaced. There is intentionally no registry Reset() here;
// mod-registered towns persist across new games and are re-populated by the
// Lua runtime at mod load time.
InitializeTristram();
currlevel = 0;
leveltype = DTYPE_TOWN;
setlevel = false;
Expand Down
1 change: 1 addition & 0 deletions Source/levels/gendung_defs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum lvl_entry : uint8_t {
ENTRY_WARPLVL,
ENTRY_TWARPDN,
ENTRY_TWARPUP,
ENTRY_TOWNSWITCH,
};

} // namespace devilution
109 changes: 50 additions & 59 deletions Source/levels/town.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
#include "engine/world_tile.hpp"
#include "game_mode.hpp"
#include "levels/drlg_l1.h"
#include "levels/town_data.h"
#include "levels/trigs.h"
#include "multi.h"
#include "player.h"
#include "quests.h"
#include "utils/endian_swap.hpp"
#include "utils/log.hpp"

namespace devilution {

Expand Down Expand Up @@ -199,48 +201,52 @@ void DrlgTPass3()
}
}

FillSector("levels\\towndata\\sector1s.dun", 46, 46);
FillSector("levels\\towndata\\sector2s.dun", 46, 0);
FillSector("levels\\towndata\\sector3s.dun", 0, 46);
FillSector("levels\\towndata\\sector4s.dun", 0, 0);

auto dunData = LoadFileInMem<uint16_t>("levels\\towndata\\automap.dun");
PlaceDunTiles(dunData.get(), { 0, 0 });

if (!IsWarpOpen(DTYPE_CATACOMBS)) {
dungeon[20][7] = 10;
dungeon[20][6] = 8;
FillTile(48, 20, 320);
const std::string &townId = GetTownRegistry().GetCurrentTown();
if (!GetTownRegistry().HasTown(townId)) {
LogError("DrlgTPass3: current town '{}' not registered", townId);
return;
}
if (!IsWarpOpen(DTYPE_CAVES)) {
dungeon[4][30] = 8;
FillTile(16, 68, 332);
FillTile(16, 70, 331);
const TownConfig &config = GetTownRegistry().GetTown(townId);
for (const auto &sector : config.sectors) {
FillSector(sector.filePath.c_str(), sector.x, sector.y);
}
if (!IsWarpOpen(DTYPE_HELL)) {
dungeon[15][35] = 7;
dungeon[16][35] = 7;
dungeon[17][35] = 7;
for (int x = 36; x < 46; x++) {
FillTile(x, 78, PickRandomlyAmong({ 1, 2, 3, 4 }));

for (const TownWarpPatch &patch : config.warpClosedPatches) {
if (IsWarpOpen(patch.requiredWarp))
continue;
for (const auto &[pos, cellVal] : patch.dungeonCells)
dungeon[pos.x][pos.y] = static_cast<uint8_t>(cellVal);
for (const TownWarpFillTile &ft : patch.fillTiles)
FillTile(ft.x, ft.y, ft.tile);
if (patch.randomGroundStrip.has_value()) {
const TownWarpClosedRandomGroundStrip &strip = *patch.randomGroundStrip;
for (int x = strip.xStart; x < strip.xEndExclusive; x++) {
FillTile(x, strip.y, PickRandomlyAmong({ 1, 2, 3, 4 }));
}
}
}
if (gbIsHellfire) {
if (IsWarpOpen(DTYPE_NEST)) {
TownOpenHive();
} else {
TownCloseHive();

if (townId == TristramTownId) {
auto dunData = LoadFileInMem<uint16_t>("levels\\towndata\\automap.dun");
PlaceDunTiles(dunData.get(), { 0, 0 });

if (gbIsHellfire) {
if (IsWarpOpen(DTYPE_NEST)) {
TownOpenHive();
} else {
TownCloseHive();
}
if (IsWarpOpen(DTYPE_CRYPT))
TownOpenGrave();
else
TownCloseGrave();
}
if (IsWarpOpen(DTYPE_CRYPT))
TownOpenGrave();
else
TownCloseGrave();
}

if (Quests[Q_PWATER]._qactive != QUEST_DONE && Quests[Q_PWATER]._qactive != QUEST_NOTAVAIL) {
FillTile(60, 70, 342);
} else {
FillTile(60, 70, 71);
if (Quests[Q_PWATER]._qactive != QUEST_DONE && Quests[Q_PWATER]._qactive != QUEST_NOTAVAIL) {
FillTile(60, 70, 342);
} else {
FillTile(60, 70, 71);
}
}

InitTownPieces();
Expand Down Expand Up @@ -358,30 +364,15 @@ void CleanTownFountain()

void CreateTown(lvl_entry entry)
{
dminPosition = { 10, 10 };
dmaxPosition = { 84, 84 };

if (entry == ENTRY_MAIN) { // New game
ViewPosition = { 75, 68 };
} else if (entry == ENTRY_PREV) { // Cathedral
ViewPosition = { 25, 31 };
} else if (entry == ENTRY_TWARPUP) {
if (TWarpFrom == 5) {
ViewPosition = { 49, 22 };
}
if (TWarpFrom == 9) {
ViewPosition = { 18, 69 };
}
if (TWarpFrom == 13) {
ViewPosition = { 41, 81 };
}
if (TWarpFrom == 21) {
ViewPosition = { 36, 25 };
}
if (TWarpFrom == 17) {
ViewPosition = { 79, 62 };
}
const std::string &townId = GetTownRegistry().GetCurrentTown();
if (!GetTownRegistry().HasTown(townId)) {
LogError("CreateTown: current town '{}' not registered", townId);
return;
}
const TownConfig &config = GetTownRegistry().GetTown(townId);
dminPosition = config.dminPosition;
dmaxPosition = config.dmaxPosition;
ViewPosition = config.GetEntryPoint(entry, TWarpFrom);

DrlgTPass3();
}
Expand Down
Loading
Loading