Skip to content

Commit 9fb9cb0

Browse files
committed
Add portal positions to town config
1 parent 0a089fb commit 9fb9cb0

File tree

5 files changed

+105
-39
lines changed

5 files changed

+105
-39
lines changed

Source/levels/town_data.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace {
99
TownRegistry g_townRegistry;
1010

1111
/** @brief Spawn used when no TownEntryPoint matches (matches legacy hard-coded default). */
12-
constexpr Point kDefaultTownEntryPoint = { 75, 68 };
12+
constexpr Point DefaultTownEntryPoint = { 75, 68 };
1313

1414
} // namespace
1515

@@ -65,6 +65,16 @@ std::string TownRegistry::GetTownBySaveId(uint8_t saveId) const
6565
return { TristramTownId };
6666
}
6767

68+
Point GetPortalTownPosition(size_t portalIndex)
69+
{
70+
if (portalIndex >= NumTownPortalSlots)
71+
return DefaultTristramPortalPositions[0];
72+
const std::string &townId = GetTownRegistry().GetCurrentTown();
73+
if (GetTownRegistry().HasTown(townId))
74+
return GetTownRegistry().GetTown(townId).portalPositions[portalIndex];
75+
return DefaultTristramPortalPositions[portalIndex];
76+
}
77+
6878
Point TownConfig::GetEntryPoint(lvl_entry entry, int warpFrom) const
6979
{
7080
// For ENTRY_TWARPUP, match both entry type and warpFromLevel
@@ -74,7 +84,7 @@ Point TownConfig::GetEntryPoint(lvl_entry entry, int warpFrom) const
7484
return ep.viewPosition;
7585
}
7686
}
77-
return kDefaultTownEntryPoint;
87+
return DefaultTownEntryPoint;
7888
}
7989

8090
// For other entry types, just match the type
@@ -85,7 +95,7 @@ Point TownConfig::GetEntryPoint(lvl_entry entry, int warpFrom) const
8595
}
8696

8797
// Default fallback
88-
return kDefaultTownEntryPoint;
98+
return DefaultTownEntryPoint;
8999
}
90100

91101
void InitializeTristram()

Source/levels/town_data.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <array>
34
#include <optional>
45
#include <string>
56
#include <unordered_map>
@@ -15,6 +16,22 @@ namespace devilution {
1516
/** @brief Canonical town registry id for vanilla Tristram (lowercase). */
1617
inline constexpr char TristramTownId[] = "tristram";
1718

19+
/** @brief Town portal spell anchor slots; must match MAXPORTAL in portal.h. */
20+
inline constexpr size_t NumTownPortalSlots = 4;
21+
22+
/** @brief Legacy positions for town portal missiles (one per player slot). */
23+
inline constexpr std::array<Point, NumTownPortalSlots> DefaultTristramPortalPositions = { {
24+
Point { 57, 40 },
25+
Point { 59, 40 },
26+
Point { 61, 40 },
27+
Point { 63, 40 },
28+
} };
29+
30+
/**
31+
* @brief World position for the town portal missile / portal entry for player slot `portalIndex`.
32+
*/
33+
Point GetPortalTownPosition(size_t portalIndex);
34+
1835
/**
1936
* @brief Represents a town sector (map piece)
2037
*/
@@ -90,6 +107,7 @@ struct TownConfig {
90107
std::vector<TownTrigger> triggers;
91108
std::vector<TownWarpPatch> warpClosedPatches;
92109
std::vector<TownerPositionOverride> townerOverrides;
110+
std::array<Point, NumTownPortalSlots> portalPositions = DefaultTristramPortalPositions;
93111

94112
/**
95113
* @brief Gets the spawn point for a given entry type and warp source

Source/lua/modules/towns.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ std::string LuaRegisterTown(std::string_view townId, const sol::table &config)
139139
}
140140
}
141141

142+
if (sol::optional<sol::table> portals = config["portals"]) {
143+
size_t slot = 0;
144+
for (const auto &kv : *portals) {
145+
if (slot >= NumTownPortalSlots) {
146+
LogWarn("registerTown: portals list has more than {} entries; ignoring extras", NumTownPortalSlots);
147+
break;
148+
}
149+
sol::table t = kv.second.as<sol::table>();
150+
sol::optional<int> px = t["x"];
151+
sol::optional<int> py = t["y"];
152+
townConfig.portalPositions[slot] = { px.value_or(0), py.value_or(0) };
153+
++slot;
154+
}
155+
}
156+
142157
std::string townIdStr(townId);
143158
GetTownRegistry().RegisterTown(townIdStr, townConfig);
144159
return townIdStr;
@@ -182,7 +197,8 @@ sol::table LuaTownsModule(sol::state_view &lua)
182197
LuaSetDocFn(table, "register", "(townId: string, config: table) -> string",
183198
"Registers a new town from a config table. Returns town ID.\n"
184199
"Optional triggers: array of tables with x, y, kind (\"nextlevel\" or \"townwarp\").\n"
185-
"For townwarp, set level (dungeon level) and warp (\"catacombs\", \"caves\", \"hell\", \"nest\", \"crypt\") for IsWarpOpen gating.",
200+
"For townwarp, set level (dungeon level) and warp (\"catacombs\", \"caves\", \"hell\", \"nest\", \"crypt\") for IsWarpOpen gating.\n"
201+
"Optional portals: up to four { x, y } tables for town portal spell positions (defaults match Tristram).",
186202
LuaRegisterTown);
187203

188204
LuaSetDocFn(table, "travel", "(townId: string)",

Source/portal.cpp

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
#include "portal.h"
77

8+
#include "levels/town_data.h"
89
#include "lighting.h"
910
#include "missiles.h"
1011
#include "multi.h"
@@ -21,14 +22,6 @@ namespace {
2122
/** Current portal number (a portal array index). */
2223
size_t portalindex;
2324

24-
/** Coordinate of each player's portal in town. */
25-
Point PortalTownPosition[MAXPORTAL] = {
26-
{ 57, 40 },
27-
{ 59, 40 },
28-
{ 61, 40 },
29-
{ 63, 40 },
30-
};
31-
3225
} // namespace
3326

3427
void InitPortals()
@@ -67,7 +60,7 @@ void SyncPortals()
6760
continue;
6861
const Player &player = Players[i];
6962
if (leveltype == DTYPE_TOWN)
70-
AddPortalMissile(player, PortalTownPosition[i], true);
63+
AddPortalMissile(player, GetPortalTownPosition(static_cast<size_t>(i)), true);
7164
else {
7265
int lvl = currlevel;
7366
if (setlevel)
@@ -80,7 +73,7 @@ void SyncPortals()
8073

8174
void AddPortalInTown(const Player &player)
8275
{
83-
AddPortalMissile(player, PortalTownPosition[player.getId()], false);
76+
AddPortalMissile(player, GetPortalTownPosition(player.getId()), false);
8477
}
8578

8679
void ActivatePortal(const Player &player, Point position, int lvl, dungeon_type dungeonType, bool isSetLevel)
@@ -163,7 +156,7 @@ void GetPortalLevel()
163156
void GetPortalLvlPos()
164157
{
165158
if (leveltype == DTYPE_TOWN) {
166-
ViewPosition = PortalTownPosition[portalindex] + Displacement { 1, 1 };
159+
ViewPosition = GetPortalTownPosition(portalindex) + Displacement { 1, 1 };
167160
} else {
168161
ViewPosition = Portals[portalindex].position;
169162

test/town_registry_test.cpp

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ using namespace devilution;
1414

1515
namespace {
1616

17-
constexpr size_t kTristramTriggerCount = 6;
18-
constexpr size_t kTristramWarpClosedPatchCount = 3;
19-
constexpr size_t kTristramEntryPointCount = 8;
17+
constexpr size_t TristramTriggerCount = 6;
18+
constexpr size_t TristramWarpClosedPatchCount = 3;
19+
constexpr size_t TristramEntryPointCount = 8;
2020

21-
constexpr Point kTristramCathedralTrigPosition = { 25, 29 };
21+
constexpr Point TristramCathedralTrigPosition = { 25, 29 };
2222

23-
constexpr Point kTownEntryDefaultFallback = { 75, 68 };
23+
constexpr Point TownEntryDefaultFallback = { 75, 68 };
2424

25-
constexpr uint8_t kTestTownAlphaSaveId = 7;
26-
constexpr uint8_t kTestTownBetaSaveId = 11;
27-
constexpr uint8_t kUnknownSaveId = 99;
25+
constexpr uint8_t TestTownAlphaSaveId = 7;
26+
constexpr uint8_t TestTownBetaSaveId = 11;
27+
constexpr uint8_t UnknownSaveId = 99;
2828

29-
constexpr int kNonMatchingWarpFromLevel = 999;
29+
constexpr int NonMatchingWarpFromLevel = 999;
3030

3131
TEST(TownRegistry, HasTown_ReturnsFalseForUnregistered)
3232
{
@@ -64,27 +64,27 @@ TEST(TownRegistry, GetTownBySaveId_FindsBySaveId)
6464
TownRegistry registry;
6565
TownConfig alpha;
6666
alpha.name = "Alpha";
67-
alpha.saveId = kTestTownAlphaSaveId;
67+
alpha.saveId = TestTownAlphaSaveId;
6868
TownConfig beta;
6969
beta.name = "Beta";
70-
beta.saveId = kTestTownBetaSaveId;
70+
beta.saveId = TestTownBetaSaveId;
7171
registry.RegisterTown("alpha", alpha);
7272
registry.RegisterTown("beta", beta);
73-
EXPECT_EQ(registry.GetTownBySaveId(kTestTownAlphaSaveId), "alpha");
74-
EXPECT_EQ(registry.GetTownBySaveId(kTestTownBetaSaveId), "beta");
73+
EXPECT_EQ(registry.GetTownBySaveId(TestTownAlphaSaveId), "alpha");
74+
EXPECT_EQ(registry.GetTownBySaveId(TestTownBetaSaveId), "beta");
7575
}
7676

7777
TEST(TownRegistry, GetTownBySaveId_ReturnsTristramForUnknownNonZeroSaveId)
7878
{
7979
TownRegistry registry;
80-
EXPECT_EQ(registry.GetTownBySaveId(kUnknownSaveId), TristramTownId);
80+
EXPECT_EQ(registry.GetTownBySaveId(UnknownSaveId), TristramTownId);
8181
}
8282

8383
TEST(TownRegistry, GetTownBySaveId_ReturnsTristramWhenZeroNotRegistered)
8484
{
8585
TownRegistry registry;
8686
TownConfig cfg;
87-
cfg.saveId = kTestTownAlphaSaveId;
87+
cfg.saveId = TestTownAlphaSaveId;
8888
registry.RegisterTown("only_alpha", cfg);
8989
EXPECT_EQ(registry.GetTownBySaveId(0), TristramTownId);
9090
}
@@ -101,11 +101,11 @@ TEST(TownConfig, GetEntryPoint_ENTRY_MAIN)
101101
TEST(TownConfig, GetEntryPoint_ENTRY_TWARPUP_MatchesWarpFromLevel)
102102
{
103103
TownConfig cfg;
104-
constexpr int kWarpFromLevel = 5;
104+
constexpr int WarpFromLevel = 5;
105105
cfg.entries = {
106-
{ ENTRY_TWARPUP, { 49, 22 }, kWarpFromLevel },
106+
{ ENTRY_TWARPUP, { 49, 22 }, WarpFromLevel },
107107
};
108-
EXPECT_EQ(cfg.GetEntryPoint(ENTRY_TWARPUP, kWarpFromLevel), Point(49, 22));
108+
EXPECT_EQ(cfg.GetEntryPoint(ENTRY_TWARPUP, WarpFromLevel), Point(49, 22));
109109
}
110110

111111
TEST(TownConfig, GetEntryPoint_ENTRY_TWARPUP_NonMatchingWarpUsesDefault)
@@ -114,14 +114,14 @@ TEST(TownConfig, GetEntryPoint_ENTRY_TWARPUP_NonMatchingWarpUsesDefault)
114114
cfg.entries = {
115115
{ ENTRY_TWARPUP, { 49, 22 }, 5 },
116116
};
117-
EXPECT_EQ(cfg.GetEntryPoint(ENTRY_TWARPUP, kNonMatchingWarpFromLevel), kTownEntryDefaultFallback);
117+
EXPECT_EQ(cfg.GetEntryPoint(ENTRY_TWARPUP, NonMatchingWarpFromLevel), TownEntryDefaultFallback);
118118
}
119119

120120
TEST(TownConfig, GetEntryPoint_EmptyEntriesUsesDefault)
121121
{
122122
TownConfig cfg;
123123
cfg.entries.clear();
124-
EXPECT_EQ(cfg.GetEntryPoint(ENTRY_MAIN), kTownEntryDefaultFallback);
124+
EXPECT_EQ(cfg.GetEntryPoint(ENTRY_MAIN), TownEntryDefaultFallback);
125125
}
126126

127127
class InitializeTristramTest : public ::testing::Test {
@@ -151,28 +151,57 @@ TEST_F(InitializeTristramTest, GetTownBySaveIdZeroReturnsTristram)
151151
TEST_F(InitializeTristramTest, TristramTriggerCount)
152152
{
153153
const TownConfig &tristram = GetTownRegistry().GetTown(TristramTownId);
154-
EXPECT_EQ(tristram.triggers.size(), kTristramTriggerCount);
154+
EXPECT_EQ(tristram.triggers.size(), TristramTriggerCount);
155155
}
156156

157157
TEST_F(InitializeTristramTest, TristramFirstTriggerIsCathedralStairs)
158158
{
159159
const TownConfig &tristram = GetTownRegistry().GetTown(TristramTownId);
160160
ASSERT_FALSE(tristram.triggers.empty());
161161
const TownTrigger &first = tristram.triggers.front();
162-
EXPECT_EQ(first.position, kTristramCathedralTrigPosition);
162+
EXPECT_EQ(first.position, TristramCathedralTrigPosition);
163163
EXPECT_EQ(first.msg, WM_DIABNEXTLVL);
164164
}
165165

166166
TEST_F(InitializeTristramTest, TristramWarpClosedPatchCount)
167167
{
168168
const TownConfig &tristram = GetTownRegistry().GetTown(TristramTownId);
169-
EXPECT_EQ(tristram.warpClosedPatches.size(), kTristramWarpClosedPatchCount);
169+
EXPECT_EQ(tristram.warpClosedPatches.size(), TristramWarpClosedPatchCount);
170170
}
171171

172172
TEST_F(InitializeTristramTest, TristramEntryPointCount)
173173
{
174174
const TownConfig &tristram = GetTownRegistry().GetTown(TristramTownId);
175-
EXPECT_EQ(tristram.entries.size(), kTristramEntryPointCount);
175+
EXPECT_EQ(tristram.entries.size(), TristramEntryPointCount);
176+
}
177+
178+
TEST_F(InitializeTristramTest, TristramPortalPositionsMatchLegacy)
179+
{
180+
const TownConfig &tristram = GetTownRegistry().GetTown(TristramTownId);
181+
for (size_t i = 0; i < NumTownPortalSlots; ++i) {
182+
EXPECT_EQ(tristram.portalPositions[i], DefaultTristramPortalPositions[i]);
183+
}
184+
}
185+
186+
TEST_F(InitializeTristramTest, GetPortalTownPositionMatchesTristramWhenCurrent)
187+
{
188+
for (size_t i = 0; i < NumTownPortalSlots; ++i) {
189+
EXPECT_EQ(GetPortalTownPosition(i), DefaultTristramPortalPositions[i]);
190+
}
191+
}
192+
193+
TEST_F(InitializeTristramTest, GetPortalTownPositionUsesTownOverrides)
194+
{
195+
TownConfig c;
196+
c.name = "PortTest";
197+
c.saveId = 7;
198+
c.portalPositions = DefaultTristramPortalPositions;
199+
c.portalPositions[0] = { 99, 88 };
200+
GetTownRegistry().RegisterTown("porttest", c);
201+
GetTownRegistry().SetCurrentTown("porttest");
202+
EXPECT_EQ(GetPortalTownPosition(0), Point(99, 88));
203+
EXPECT_EQ(GetPortalTownPosition(1), DefaultTristramPortalPositions[1]);
204+
GetTownRegistry().SetCurrentTown(TristramTownId);
176205
}
177206

178207
} // namespace

0 commit comments

Comments
 (0)